/**
 * Copyright © 2011 Creativor Studio.
 * About license information, please see LICENSE.txt.
 */
package org.creativor.rayson.transport.server;

import java.io.IOException;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.creativor.rayson.transport.api.Connection;
import org.creativor.rayson.util.Log;

// TODO: Auto-generated Javadoc
/**
 * This listener will listen all asynchronous read and write events happens in
 * all the RPC connection in server side.
 * 
 * @author Nick Zhang
 */
class Listener extends Thread
{
	private static AtomicLong UID = new AtomicLong(0);
	private static Logger LOGGER = Log.getLogger();
	private Selector selector;
	private TransportServer server;
	private boolean running = true;
	
	/**
	 * Instantiates a new listener.
	 * 
	 * @param server
	 *            the server
	 * @throws IOException
	 *             Signals that an I/O exception has occurred.
	 */
	public Listener(TransportServer server) throws IOException
	{
		setName("Server " + server.toString() + " lisenter");
		this.server = server;
		this.selector = Selector.open();
		server.getSocketChannel().register(selector, SelectionKey.OP_ACCEPT);
	}
	
	/**
	 * Quit this listener thread.
	 */
	public void quit()
	{
		this.running = false;
	}
	
	/**
	 * Accept new connection.
	 * 
	 * @param key
	 */
	private void doAccept(SelectionKey key)
	{
		ServerSocketChannel channel = (ServerSocketChannel) key.channel();
		RpcConnection connection = null;
		
		try
		{
			SocketChannel clientChannel = channel.accept();
			clientChannel.configureBlocking(false);
			// register read events for the new connection.
			SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);
			connection =
					new RpcConnection(UID.incrementAndGet(), clientChannel,
							this.server.getPacketManager(), clientKey, this.server.getConfig());
			clientKey.attach(connection);
			// try
			// {
			// this.server.getConnectionManager().acceptPending(connection);
			// } catch (DenyServiceException e)
			// {
			// connection.denyToAccept();
			// }
		} catch (IOException e)
		{
			LOGGER.log(Level.SEVERE, "Accept new connection error", e);
			return;
		}
		
		if (server.getConfig().debug())
			LOGGER.info("new connection: " + connection + " accepted.");
	}
	
	/**
	 * Handling read event in server socket channel.
	 * 
	 * @param key
	 */
	private void doRead(SelectionKey key)
	{
		Connection connection = (Connection) key.attachment();
		int readCount = -1;
		try
		{
			readCount = connection.read();
		} catch (IOException e)
		{
			// Ignore this exception.
		}
		if (readCount == -1)
		{
			try
			{
				connection.close();
			} catch (IOException e)
			{
				// Do nothing about this exception.
			}
			this.server.getConnectionManager().remove(connection);
			if (server.getConfig().debug()) LOGGER.info(connection.toString() + " removed!");
		}
		
	}
	
	/**
	 * Run.
	 */
	@Override
	public void run()
	{
		LOGGER.info(getName() + " starting...");
		
		SelectionKey key;
		
		Iterator<SelectionKey> iterator;
		
		while (running)
		{
			
			try
			{
				
				selector.select();
				
				for (iterator = selector.selectedKeys().iterator(); iterator.hasNext();)
				{
					key = iterator.next();
					iterator.remove();
					if (key.isValid())
					{
						if (key.isAcceptable())
							doAccept(key);
						else if (key.isReadable())
							doRead(key);
						else if (key.isWritable())
							doWrite(key);
						else
						{
							LOGGER.log(Level.SEVERE,
									"Listener got un-excepted key: " + key.toString());
						}
					}
				}
			} catch (Throwable e)
			{
				// protect this thread not to quit.
				LOGGER.log(Level.SEVERE, "Server listener got error", e);
			}
			
		}
		LOGGER.info(getName() + " stopped");
		
	}
	
	/**
	 * Handling write event in server socket channel.
	 * 
	 * @param key
	 */
	private void doWrite(SelectionKey key)
	{
		Connection connection = (Connection) key.attachment();
		try
		{
			connection.write();
		} catch (IOException e)
		{
			try
			{
				connection.close();
			} catch (IOException e1)
			{
				// Do nothing about this exception.
			}
			this.server.getConnectionManager().remove(connection);
			if (server.getConfig().debug()) LOGGER.info(connection.toString() + " removed!");
		}
	}
}
